/*
 * server.c
 *
 *  Created on: 06-06-2012
 *      Author: Mateusz Matrejek
 *
 */
#include <fcntl.h>
#include "request.h"
#include "backup.h"
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ftw.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>

unsigned char final[MD5_DIGEST_LENGTH];

//configuration structure
struct Configuration config;

//clients array
struct Client clients[MAX_CLIENTS];
int currentConnected=0;

int socketFD;
struct sockaddr_in serv_addr;

time_t lastScan;

int hash_compare(unsigned char a[MD5_DIGEST_LENGTH],unsigned char b[MD5_DIGEST_LENGTH] )
{
  int i;
        for (i=0;i<MD5_DIGEST_LENGTH;i++)
                if (a[i]!=b[i])
                        return -1;
        return 0;
}

void md5generate(FILE *f)
{
         const int bufferSize=1024;
         MD5_CTX ctx; //deklaracja struktury typu MD5_CTX
         MD5_Init(&ctx); //inicjalizacja MD5_CTX
         unsigned char bufor[bufferSize]; //deklaracja zmiennej buforowej o rozmiarze size
         int newPart; //zmienna do której będziemy pobierali dane

         while (!feof(f))
                   {
                        newPart=fread(bufor,1,bufferSize,f); //odczytanie kolejnej porcji pliku
                        MD5_Update(&ctx,bufor,newPart); //wlasciwe hashowanie kolejnych kawalkow pliku
                   }
         MD5_Final(final,&ctx);//przepisanie wyniku do tabllicy tab

         int i;
         for (i=0;i<MD5_DIGEST_LENGTH;i++)
             printf("%02x",final[i]);
         printf("\n");
}

void loadConfig(void)
{
  strcat(config.server_path,"/home/student/Pulpit/myServer");
  strcat(config.history_path,"/home/student/Pulpit/backups");
  config.update_interval=15;
  config.port=6666;
  strcat(config.passphrase,"6d61746575737a6d617472656a656b");
}

int getFree(){
  int i;
  for (i=0;i<MAX_CLIENTS;i++)
    {
    if (clients[i].active==FALSE)
      {
      return i;
      }
    }
  return -1;
}

int authenticate(char givenPass[MAX_PASSWD_LENGTH])
{
  if (!strcmp(givenPass,config.passphrase))
    {
    return TRUE;
    }
  return FALSE;
}

void copyToBackups(char filename[MAX_PATH_LENGTH])
{
  char dirName[MAX_PATH_LENGTH];

  strcpy(dirName,config.history_path);
  strcat(dirName,"/");
  strcat(dirName,filename);
  //printf("%s\n",dirName);
  char * pch;
  pch=strrchr(dirName,'/');
  dirName[pch-dirName+1]='\0';
  printf("%s\n",dirName);


  struct stat finalBackupDir;

  if(stat(dirName,&finalBackupDir) < 0)
    {
    char commandMdir[MAX_PATH_LENGTH];
    strcpy(commandMdir,"/bin/mkdir -p ");
    strcat(commandMdir,dirName);
    if (system(commandMdir)==-1)
      {
      perror("Directory creation error. Sync may be corrupted.");
      }
    }

  //date preparation begin
  time_t tt;
  tt=time(NULL);
  struct tm *date;
  date = gmtime(&tt);
  char currDate[20];
  sprintf(currDate,"%d_%d_%d_%d_%d_%d",date->tm_year+1900,date->tm_mon+1,date->tm_mday,date->tm_hour,date->tm_min,date->tm_sec);
  //date preparation end

  char finalPath[MAX_PATH_LENGTH];
  strcat(finalPath,config.history_path);
  strcat(finalPath,"/");
  strcat(finalPath,filename);
  strcat(finalPath,"_");
  strcat(finalPath,currDate);
  printf("%s\n",finalPath);

  //prepare command
  char *command;
  command = malloc(sizeof(char)*MAX_PATH_LENGTH);

  if (command == NULL)
    {
      perror("Memory error");
      exit(1);
    }
  strcpy(command,"");
  strcat(command,"cp ");
  strcat(command,config.server_path);
  strcat(command,filename);
  strcat(command," ");
  strcat(command,finalPath);
  printf("%s\n",command);
}

void initializeSockets(void)
{
  socketFD = socket(AF_INET, SOCK_STREAM, 0);
  if (socketFD<0 )
    {
      perror("Socket creation error");
      exit(1);
    }

  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(config.port);

  if (bind(socketFD, (struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
    {
      perror("Error during socket binding");
      exit(1);
    }
  if (listen(socketFD,MAX_CLIENTS)==-1)
    {
      perror("Error while receiving connection");
      exit(1);
    }
}

void* listenClient(void *arg)
{
  int userInd=(int)arg-1;
  printf("%d\n",userInd);
  fflush(stdout);
  struct WorkingRequest wReq;
  struct WorkingRequest response;

  response.operation=REQ_RESP;
  strcpy(response.path,wReq.path);
  while(1)
    {
      int n = read(clients[userInd].socket,&wReq,sizeof(struct WorkingRequest));

      if (n<0){
        perror("Error while receiving data. Sync may be corrupted");
      }

      switch (wReq.operation){
        case REQ_LOGOUT:
            printf("%d send logout request",userInd);
            fflush(stdout);
            clients[userInd].active=FALSE;
            currentConnected--;
            pthread_exit(0);
        case REQ_QUERY:
            if(wReq.isDirectory)
              {
              struct stat st;
              char path[MAX_PATH_LENGTH];
              path[0]='\0';
              strcat(path,config.server_path);
              strcat(path,wReq.path);
              if (stat(path,&st)==-1)
                {
                  char command[MAX_PATH_LENGTH];
                  command[0]='\0';
                  strcat(command,"mkdir -p ");
                  strcat(command,path);
                  printf("%s\n",command);
                  fflush(stdout);
                  if (system(command)!=0)
                    {
                    perror("Error while creating directory. Sync may be corrupted");
                    }
                  response.type=TYP_OK;
                }
              }
            else
              {
                struct stat st;
                char path[MAX_PATH_LENGTH];
                path[0]='\0';
                strcat(path,config.server_path);
                strcat(path,wReq.path);
                if (stat(path,&st)==-1)
                {
                  if (wReq.mtime > lastScan)
                    {
                      copyToBackups(path);
                      response.type=TYP_SENDF;
                      response.size=wReq.size;
                    }
                  else
                    {
                      response.type=TYP_REMVF;
                      copyToBackups(path);
                    }
                }
              else
                {
                  FILE * ptr = fopen(path,"r");
                  md5generate(ptr);
                  fclose(ptr);

                  if (!hash_compare(final,wReq.hash))
                    {
                        if (st.st_mtime>wReq.mtime)
                          {
                            copyToBackups(path);
                            response.type=TYP_RECVF;
                            response.size=st.st_size;
                          }
                        else
                          {
                            copyToBackups(path);
                            response.type=TYP_SENDF;
                            response.size=wReq.size;
                          }
                    }
                  else
                    {
                      response.type=TYP_OK;
                    }
                }
              }
            break;
      }

      n = write(clients[userInd].socket,&response,sizeof(struct WorkingRequest));
      if (n < 0)
           perror("Blad write()");

      int fd;
      char buffer[response.size];
      switch(response.type){
                case(TYP_OK):
                case(TYP_REMVF):
                    break;
                case(TYP_SENDF):
                    fd = open(response.path,O_WRONLY);
                    read(clients[userInd].socket,&buffer,response.size);
                    write(fd,&buffer,response.size);
                    close(fd);
                    break;
                case(TYP_RECVF):
                    fd = open(response.path,O_RDONLY);
                    read(fd,&buffer,response.size);
                    write(clients[userInd].socket,&buffer,response.size);
                    close(fd);
                    break;

                    break;
      };
      lastScan=time(NULL)-config.update_interval;
    }
  return NULL;
}

void listenLogin(void)
{

  struct Client newClient;
  struct LoginRequest loginReq;
  struct LoginResponse loginRes;

  socklen_t clilen;
  clilen = sizeof(newClient.address);
  newClient.socket = accept(socketFD, (struct sockaddr *) &newClient.address,&clilen);
  if (newClient.socket < 0)
    {
      perror("Error while receiving login data");
      return ;
    }

  int n = read(newClient.socket,&loginReq,sizeof(struct LoginRequest));
  if (n < 0) perror("blad read()");

  if (currentConnected<MAX_CLIENTS)
    {
      if (authenticate(loginReq.passphrase))
        {
            loginRes.response=RESP_ACCEPT;
            strcpy(newClient.name,loginReq.name);
            currentConnected++;
            newClient.id=currentConnected;
            newClient.active=TRUE;
            int newIndex = getFree();
            clients[newIndex]=newClient;
            pthread_create(&clients[currentConnected-1].listenThread,NULL,&listenClient,(void*)clients[currentConnected-1].id);
        }
      else
        {
            loginRes.response=RESP_DEINED;
        }
    }
  else
    {
      loginRes.response=RESP_SFULL;
    }

  n = write(newClient.socket,&loginRes,sizeof(struct LoginResponse));
  if (n < 0)
       perror("Blad write()");

  printf("OK\n");
}

int main()
{
  lastScan=1;
  loadConfig();
  initializeSockets();

  while(TRUE)
    {
      listenLogin();
    }
  return 0;
}
